package view;

import java.util.List;
import engine.Cell;
import engine.LayerAgents;
import engine.LayerFluids;
import engine.Simulation;
import model.Model;
import utils.ResizableCanvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

/**
 * Base class to view the simulation.
 *
 * <p>Internal units (for instance, for x0 and y0) are in um (micrometers). We
 * do not use JavaFX affine transformations because they don't handle well
 * real numbers (only integers). We use our own routines. See for instance
 * {@link xToCanvasUnits}.</p>
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-12
 */
public abstract class SimulationView extends ResizableCanvas
{
	/* zoom is in pixels per um -- how many pixels are occuped by an um
	 * (micrometer).
	 */
	protected double zoom;
	/* offset variables (in um units) */
	protected double xc, yc;

	protected SimulationView(double zoom) {
		this.zoom = zoom;
		xc = Model.getPetriWidth()/2;
		yc = Model.getPetriHeight()/2;
	}

	// support routines -- offset for the widget
	// when using (x0,y0) as the left-up corner. instead of (xc,yc)
	protected double x0() { return xc - (getWidth()/2)/zoom; }
	protected double y0() { return yc - (getHeight()/2)/zoom; }

	protected double xToCanvasUnits(double um_units) { return (um_units-x0())*zoom; }
	protected double yToCanvasUnits(double um_units) { return (um_units-y0())*zoom; }

	/**
	 * Repaints the view; do it from time to time.
	 *
	 * @param gc the {@link GraphicsContext}
	 */
	protected void draw(GraphicsContext gc, double width, double height) {
		gc.setFill(Color.BLACK);
		gc.fillRect(0, 0, width, height);

		/* We want to draw only the molecules (and cells) within the screen. Let
		 * (i,j) be the molecules grid indices. Since the first molecule may be
		 * only partially visible, we must find the correct x0 offset for the
		 * layer at hand, by converting x0 to i of the first molecule, and then
		 * convert back i to a x0.
		 */

		// molecules
		LayerFluids fluids = Simulation.get().getFluidsLayer();
		synchronized(fluids) {
			for(int i = (int)(x0()/20); i < Math.ceil((x0()+(getWidth()/zoom))/20); i++)
				for(int j = (int)(y0()/20); j < Math.ceil((y0()+(getHeight()/zoom))/20); j++) {
					double n = Simulation.get().getFluidsLayer().getAmount(i*20,j*20);
					if(n > 0) {
						gc.setFill(new Color(0,1,0,Math.min(1,n/Model.M.get())));
						double x = xToCanvasUnits(i*20);
						double y = yToCanvasUnits(j*20);
						double size = 20*zoom;
						gc.fillRect(x, y, size, size);
					}
				}
		}

		// cells
		LayerAgents cells = Simulation.get().getAgentsLayer();
		int imin = (int)(x0()/LayerAgents.REGION_SIZE);
		int jmin = (int)(y0()/LayerAgents.REGION_SIZE);
		int imax = (int)Math.ceil((x0()+(getWidth()/zoom))/LayerAgents.REGION_SIZE);
		int jmax = (int)Math.ceil((y0()+(getHeight()/zoom))/LayerAgents.REGION_SIZE);
		synchronized(cells) {
			for(int i = imin; i < imax; i++)
				for(int j = jmin; j < jmax; j++) {
					List<Cell> cs = Simulation.get().getAgentsLayer().getCellsRegion(i,j);
					if(cs != null)
						for(Cell cell: cs) {
							double x = xToCanvasUnits(cell.getX());
							double y = yToCanvasUnits(cell.getY());
							double size = 4*zoom;
							drawCell(gc, cell, x, y, size);
						}
				}
		}
	}

	protected void drawCell(GraphicsContext gc, Cell cell, double x, double y, double size) {
		gc.setFill(Model.getCellColor(cell.getType()));
		if(zoom >= 0.75) gc.fillOval(x, y, size, size);
		else gc.fillRect(x, y, size, size);
	}
}
